/*
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ST_EXT-S_UIMAST.
 *
 * The Initial Developer of the Original Code is
 * University of Rome, Tor Vergata
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 */

package it.uniroma2.art.uimast.impl;

import it.uniroma2.art.ontapi.ARTRepository;
import it.uniroma2.art.ontapi.ARTResource;
import it.uniroma2.art.semanticturkey.resources.Resources;
import it.uniroma2.art.uimast.xpts.ExportModeDriver;

import java.util.HashSet;
import java.util.Iterator;
import org.apache.uima.cas.*;
import org.w3c.dom.*;

/**
 * Projects a class into an annotation type in a type system.
 * @author <a href="mailto:manuel.fiorelli@gmail.com">Manuel Fiorelli</a>
 */
class ClassProjection extends CommonDOMFacilities
{
	/**
	 * Constructs a <code>ClassProjection</code> object.
	 * @param annotationProjection the owner annotation projection
	 * @param e the DOM element describing the class projection
	 * @throws Exception
	 */
    public ClassProjection(AnnotationProjection annotationProjection, Element e)
        throws Exception
    {
    	// Stores the owner annotation projection.
    	this.annotationProjection = annotationProjection;
    	    
    	// Creates an hash set to store the feature projections.
        features = new HashSet<FeatureProjection>();
        
        // Gets the ontology repository interface.
        ARTRepository repository = Resources.getRepository();
        
        // Checks that the 'e' has the 'class' attribute.
        if(!hasAttributeNS(e, AnnotationProjection.PROJ_NS, "class"))
            throw new Exception("Missing class name");
        
        // Gets the 'class' attribute of the 'e' element.
        String className = getAttributeNS(e, AnnotationProjection.PROJ_NS, "class");
        
        // Checks that the 'e' has the 'into' attribute.
        if(!hasAttributeNS(e, AnnotationProjection.PROJ_NS, "into"))
            throw new Exception("Missing type name");
        
        // Gets the 'into' attribute of the 'e' element.      
        String typeName = getAttributeNS(e, AnnotationProjection.PROJ_NS, "into");
        
        // Stores the source class, if any.
        srcClass = repository.getSTClass(className);
        
        // Checks that the source class exists.
        if(srcClass == null)
            throw new Exception("The class name \"" + className + "\" doesn't exist");
        
        // Gets the current CAS type system.
        TypeSystem typeSystem = getCAS().getTypeSystem();
        
        // Stores the target type, if any.
        destType = typeSystem.getType(typeName);
        
        // Checks that the destination type exists.
        if(destType == null)
            throw new Exception("The type name \"" + typeName + "\" doesn't exist");
        
        // Checks that the destination type extends or is equal to the annotation base type.
        if(!destType.equals(annotationProjection.getAnnotationBaseType()) && !typeSystem.subsumes(annotationProjection.getAnnotationBaseType(), destType))
            throw new Exception("The type \"" + typeName + "\" isn't a valid annotation type");
        
        //// Manages the feature projections.
        
        // Gets the 'feature' elements of the 'e' element.
        NodeList featureList = e.getElementsByTagNameNS(AnnotationProjection.PROJ_NS, "feature");
        for(int i = 0; i < featureList.getLength(); i++)
        {
        	// Gets the i-th child.
            Element f = (Element)featureList.item(i);
            
            // Manages the projection of that feature of the target type.
            FeatureProjection featureProjection = FeatureProjection.create(this, f);
            
            // Avoid duplicates.
            if(!features.add(featureProjection))
                throw new Exception("Duplicated feature definition");
        }

    }

    /**
     * Constructs a <code>ClassProjection</code> object.
     * @param srcClass the source class
     * @param annotationProjection the owner annotation projection
     * @param destType the destination type
     * @throws Exception
     */
    public ClassProjection(ARTResource srcClass, AnnotationProjection annotationProjection, String destType)
        throws Exception
    {
    	// Stores the owner annotation projection
    	this.annotationProjection = annotationProjection;
    	
    	// Creates an hash set to store the feature projections.
        features = new HashSet<FeatureProjection>();
        
        // Stores the source class.
        this.srcClass = srcClass;
        
        // Gets the current CAS type system.
        TypeSystem typeSystem = annotationProjection.getCAS().getTypeSystem();
        
        // Gets the destination type.
        this.destType = typeSystem.getType(destType);
        
        // Checks that the destination type exists.
        if(this.destType == null)
            throw new Exception("The destination type \"" + "\" doesn't exist");
    }

    /**
     * Gets the source class.
     * @return an ontology class
     */
    public ARTResource getSourceClass()
    {
        return srcClass;
    }

    /**
     * Gets the destination type.
     * @return a type
     */
    public Type getDestinationType()
    {
        return destType;
    }

    /**
     * Adds a feature projection.
     * @param shortName the feature short name
     * @param mode the projection mode: either <code>value</code> or <code>property</code>
     * @param value the value lexical representation or the property URI
     * @throws Exception
     */
    public void addFeatureProjection(String shortName, String mode, String value)
        throws Exception
    {
    	// Creates the appropriate feature projection
        FeatureProjection f = FeatureProjection.create(this, shortName, mode, value);
        
        // Records the new feature projection (checking for duplicates) 
        if(!features.add(f))
            throw new Exception("The feature \"" + f.getFeature().getName() + "\" is already projected");    
    }

    /**
     * Removes a given feature projection.
     * @param shortName the feature short name
     * @throws Exception
     */
    public void removeFeatureProjection(String shortName)
        throws Exception
    {
    	// Iterates over the feature projections.
        for(Iterator<FeatureProjection> it = features.iterator(); it.hasNext();)
            if(it.next().getFeature().getShortName().equals(shortName))	// removes the projection for a feature with a given name
            {
                it.remove();
                return;
            }

    }

    /**
     * Applies this class projection to the ordered pair <code>&lt;domainObj, annot&gt;</code>
     * @param domainObject the domain object
     * @param annot the annotation
     * @param fs the feature structure
     */
    public void apply(ARTResource domainObject, ARTResource annot, FeatureStructure fs)
    {
    	// Applies every feature projection to the current pair <domainObject, annot>
        for(FeatureProjection fp : features) {
        	fp.apply(domainObject, annot, fs);
        }
    }

    /**
     * Serializes this class projection.
     * @param e A DOM element to augment
     */
    public void toDOM(Element e)
    {
        Document doc = e.getOwnerDocument();
        Element p = doc.createElement("project");
        e.appendChild(p);
        p.setAttribute("class", srcClass.getURI());
        p.setAttribute("into", destType.getName());

        for(FeatureProjection fp : features) {
        	fp.toDOM(p);
        }
    }
    
    /**
     * Gets the current CAS.
     * @return a CAS
     */
    public CAS getCAS() {
    	return annotationProjection.getCAS();
    }
    
    /**
     * Gets the current export mode provider.
     * @return the export mode provider
     */
    public ExportModeDriver getExportModeProvider() {
    	return annotationProjection.getExportModeProvider();
    }
    
    /** The owner annotation projection. */
    private AnnotationProjection annotationProjection;
    
    /** The source class. */
    private ARTResource srcClass;
        
    /** The destination type. */
    private Type destType;
    
    /** The destination type features projections. */
    private HashSet<FeatureProjection> features;
}
